﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Shell.PocoDot;

namespace Shell.DomainDot
{
    public class WithOneInjector : DomainInjector
    {
        private static Type mainSegmentType;
        private static Type typeSupported;
        private IDataContextFactory pocoFactory;
        private IRepositoryFactory relationshipFactory;

        static WithOneInjector()
        {
            typeSupported = typeof(WithOne<>);
            mainSegmentType = typeof(Main<>);
        }

        public WithOneInjector()
        {
            var seeker = ObjectSeeker.Seeker;
            this.pocoFactory = seeker.Seek<IDataContextFactory>();
            this.relationshipFactory = seeker.Seek<IRepositoryFactory>();
        }

        protected virtual Relationships Relationship
        {
            get { return Relationships.WithOne; }
        }

        protected override Type TargetDomainType
        {
            get { return typeSupported; }
        }

        protected override IEnumerable<PropertyInfo> GetInjectingProperties<TDomainDot>(TDomainDot dot)
        {
            if (dot.Context.Key.HasValue)
            {
                return base.GetInjectingProperties<TDomainDot>(dot);
            }
            return Enumerable.Empty<PropertyInfo>();
        }

        protected override object CreateDomainDot<TDomainDot>(TDomainDot dot, PropertyInfo domainSegment)
        {
            if (dot.Context.Key.HasValue)
            {
                var c = domainSegment.PropertyType.GetConstructors()[0];
                var respository = this.MakePocoRepository(domainSegment);
                var relationshipRespository = this.MakeRelationRepository(dot, domainSegment);
                if (respository == null || relationshipRespository == null)
                {
                    throw new InvalidOperationException("Could not make respository for the specified domain dot.");
                }
                return c.Invoke(new object[] { dot.Context.Key.Value, respository, relationshipRespository });
            }
            return null;
        }

        protected virtual IPersistent MakePocoRepository(PropertyInfo domainSegment)
        {
            var pocoArgument = this.GetPocoType(domainSegment);
            if (pocoArgument == null)
            {
                //TODO
            }
            var factoryType = this.pocoFactory.GetType();
            var createMethod = factoryType.GetMethod("Create");
            var m = createMethod.MakeGenericMethod(pocoArgument);
            var repository = m.Invoke(this.pocoFactory, new object[] { });
            return repository == null ? null : repository as IPersistent;
        }

        protected virtual IRepository MakeRelationRepository<TDomainDot>(TDomainDot dot, PropertyInfo domainSegment)
            where TDomainDot : DomainDotBase
        {
            var mainPocoType = this.GetMainSegmentPocoType(dot);
            var pocoType = this.GetPocoType(domainSegment);
            if (mainPocoType != null && pocoType != null)
            {
                return this.relationshipFactory.Create(mainPocoType, pocoType);
            }
            return null;
        }

        protected virtual Type GetPocoType(PropertyInfo domainSegment)
        {
            var t = domainSegment.PropertyType;
            if (t.IsGenericType && t.GetGenericTypeDefinition() == this.TargetDomainType)
            {
                return t.GetGenericArguments()[0];
            }
            return null;
        }

        protected override void OnDispose(bool disposing)
        {
            if (disposing)
            {
                this.pocoFactory = null;
                this.relationshipFactory = null;
            }
        }
    }
}
